/*
 * Copyright (c) 2022, smartmx - smartmx@qq.com
 * Copyright (c) 2008, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include "ringbuf.h"

void ringbuf_init(struct ringbuf *r, uint8_t *dataptr, RINGBUF_INDEX_CONF_TYPE size)
{
    r->data = dataptr;
    r->mask = size - 1;
    r->put_ptr = 0;
    r->get_ptr = 0;
}

int ringbuf_put(struct ringbuf *r, uint8_t c)
{
    /* Check if buffer is full. If it is full, return 0 to indicate that
       the element was not inserted into the buffer.

       XXX: there is a potential risk for a race condition here, because
       the ->get_ptr field may be written concurrently by the
       ringbuf_get() function. To avoid this, access to ->get_ptr must
       be atomic. We use RINGBUF_INDEX_CONF_TYPE, users cans change to
       the type which makes access atomically,
       but C does not guarantee this.
    */
    if (((r->put_ptr - r->get_ptr) & r->mask) == r->mask)
    {
        return 0;
    }
    /*
     * CC_ACCESS_NOW is used because the compiler is allowed to reorder
     * the access to non-volatile variables.
     * In this case a reader might read from the moved index/ptr before
     * its value (c) is written. Reordering makes little sense, but
     * better safe than sorry.
     */
    CC_ACCESS_NOW(uint8_t, r->data[r->put_ptr]) = c;
    CC_ACCESS_NOW(RINGBUF_INDEX_CONF_TYPE, r->put_ptr) = (r->put_ptr + 1) & r->mask;
    return 1;
}

int ringbuf_get(struct ringbuf *r)
{
    uint8_t c;

    /* Check if there are bytes in the buffer. If so, we return the
       first one and increase the pointer. If there are no bytes left, we
       return -1.

       XXX: there is a potential risk for a race condition here, because
       the ->put_ptr field may be written concurrently by the
       ringbuf_put() function. To avoid this, access to ->get_ptr must
       be atomic. We use RINGBUF_INDEX_CONF_TYPE, users cans change to
       the type which makes access atomically,
       but C does not guarantee this.
    */
    if (((r->put_ptr - r->get_ptr) & r->mask) > 0)
    {
        /*
         * CC_ACCESS_NOW is used because the compiler is allowed to reorder
         * the access to non-volatile variables.
         * In this case the memory might be freed and overwritten by
         * increasing get_ptr before the value was copied to c.
         * Opposed to the put-operation this would even make sense,
         * because the register used for mask can be reused to save c
         * (on some architectures).
         */
        c = CC_ACCESS_NOW(uint8_t, r->data[r->get_ptr]);
        CC_ACCESS_NOW(RINGBUF_INDEX_CONF_TYPE, r->get_ptr) = (r->get_ptr + 1) & r->mask;
        return c;
    }
    else
    {
        return -1;
    }
}

int ringbuf_size(struct ringbuf *r)
{
    return r->mask + 1;
}

int ringbuf_elements(struct ringbuf *r)
{
    return (r->put_ptr - r->get_ptr) & r->mask;
}

